package com.iflytek.elpmobile.utils.network;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

import com.iflytek.elpmobile.utils.Logging;
import com.iflytek.elpmobile.utils.ReadFileUtils;
import com.simple.elpmobile.utils.network.model.NetworkStatus;
import com.simple.elpmobile.utils.network.model.NetworkStatusListener;
import com.simple.elpmobile.utils.network.model.NetworkStatusReason;

public class DownloadTask extends AbstractTask
{
	private static final String TAG = "DownloadTask";

	public static final int TIME_OUT = 20 * 1000;
	
	private static final int PROCESS_TIME_DELAY = 1000;
	private static final int READ_BUF_SIZE = 4 * 1024;
	private static final int READ_BLOCK_SIZE = 512 * 1024;
	
	private long mStartSize;
	private long mCurrentSize;
	private long mTotalSize;
	
	private boolean mIsSupportRange;
	
	private String mPath;
	
	private Timer mProcessTimer;
	private ProcessTimerTask mTimerTask;
	
	private RandomAccessFile mFile;
	private HttpParams mHttpParameters;
	
	DownloadTask(String url, String path, NetworkStatusListener statusListener) 
	{
		super(url, statusListener);
		
		mPath = path;
	}

	private int getFileTotalSize() throws IOException
	{
		HttpURLConnection connection = (HttpURLConnection) mURL.openConnection();
		connection.setConnectTimeout(TIME_OUT);
		
		int length = connection.getContentLength();
		connection.disconnect();
		return length;
	}
	
	private NetworkStatusReason preparaFile(long totalSize)
	{
		long fileLength = 0;
		File file = null;
		try {
			file = new File(mPath);
			boolean isFileExists = file.exists();
			if (!mIsSupportRange && isFileExists) 
			{
				file.delete();
				isFileExists = false;
			}
			
			if (!isFileExists) file.createNewFile();
		
			fileLength = file.length();
		} 
		catch (IOException e) 
		{
			Logging.e(TAG, String.format("createNewFile error!e.getMessage:%s", e.getMessage()));
			
			return NetworkStatusReason.IOError;
		}
		
		if (fileLength > totalSize) return NetworkStatusReason.LocalFileRangeError;
		
		try 
		{
			mFile = new RandomAccessFile(file, "rw");
			mFile.seek(fileLength);
		} 
		catch (Exception e)
		{
			Logging.e(TAG, String.format("new RandomAccessFile error!e.getMessage:%s", e.getMessage()));
			
			return NetworkStatusReason.IOError;
		}
		
		mCurrentSize = mStartSize = fileLength;
		return NetworkStatusReason.OK;
	}
	
	private boolean isSupportRange()
	{
		HttpGet httpGet = null;
		HttpClient client = null;
		
		client = new DefaultHttpClient(mHttpParameters);
		httpGet = new HttpGet(mURL.toString());
		httpGet.addHeader("RANGE", "bytes=" + 0 + "-"
					+ 1);  
		
		try 
		{
			HttpResponse httpResponse = client.execute(httpGet);
			if (httpResponse.getStatusLine().getStatusCode() != HttpURLConnection.HTTP_PARTIAL) return false;
		}
		catch (Exception e) 
		{
			return false;
		}
		
		closeBlockConn(null, httpGet, client);
		return true;
	}
	
	private NetworkStatusReason getFile(byte[] buf, long start, long length) 
	{
		InputStream inStream = null;
		HttpGet httpGet = null;
		HttpClient client = null;
		
		boolean isRange = false;
		try 
		{
			client = new DefaultHttpClient(mHttpParameters);
			httpGet = new HttpGet(mURL.toString());
			if (!(0 == start && mTotalSize == length))
			{
				httpGet.addHeader("RANGE", "bytes=" + start + "-"
							+ (start + length - 1));  
				isRange = true;
			}
			
			HttpResponse httpResponse = client.execute(httpGet);
			/*
			conn = (HttpURLConnection) mURL.openConnection();
			conn.setConnectTimeout(TIME_OUT);
			conn.setRequestMethod("GET");
			conn.setRequestProperty("Range", "bytes=" + start + "-"
					+ (start + length - 1));
			
			if (conn.getResponseCode() != HttpURLConnection.HTTP_PARTIAL)
			{
				throw new IOException("conn.getResponseCode() != HttpURLConnection.HTTP_PARTIAL");
			}*/
			if (httpResponse.getStatusLine().getStatusCode() != (isRange ? HttpURLConnection.HTTP_PARTIAL : HttpURLConnection.HTTP_OK))
			{
				Logging.e(TAG, String.format("conn.getResponseCode():StatusCode:%d", httpResponse.getStatusLine().getStatusCode()));
				
				closeBlockConn(inStream, httpGet, client);
				return NetworkStatusReason.ServerRangeError;
			}
			
			inStream = httpResponse.getEntity().getContent();
		} 
		catch (IOException e)
		{
			Logging.e(TAG, String.format("getFileBlock openConnection error!e.getMessage:%s", e.getMessage()));
			
			closeBlockConn(inStream, httpGet, client);
			return NetworkStatusReason.ConnectionError;
		}
		
		int readSize = -1;
		try
		{
			while (!mNeedCancel && 
					-1 != (readSize = inStream.read(buf, 0, READ_BUF_SIZE)))
			{
				try 
				{
					mFile.write(buf, 0, readSize);
					mCurrentSize += readSize;
				} 
				catch (IOException e) 
				{
					closeBlockConn(inStream, httpGet, client);
					//提Resource leak: 'inStream' is not closed at this location ！！！纯属瞎报！！！
					return NetworkStatusReason.IOError;
				}
			}
		} 
		catch (IOException e) 
		{
			closeBlockConn(inStream, httpGet, client);
			return NetworkStatusReason.NetError;
		}
		
		closeBlockConn(inStream, httpGet, client);
		return NetworkStatusReason.OK;
	}
	
	private void closeBlockConn(InputStream inStream, HttpRequestBase requestBase, HttpClient client)
	{
		ReadFileUtils.closeCloseable(inStream);
		if (null != requestBase) requestBase.abort();
		if (null != client) 
		{
			 ClientConnectionManager connectManager = client.getConnectionManager();
			 if (null != connectManager) connectManager.shutdown();
			
		}
	}
	
	private int getProcess()
	{
		if (0 == mTotalSize) return 0;
		return (int)(((double)mCurrentSize / mTotalSize) * 100);
	}
	
	@Override
	public boolean init() 
	{
		NetworkStatusReason reason = mNetworkInfo.getReason();
		NetworkStatus status = mNetworkInfo.getStatus();
		
		do
		{
			if (NetworkStatus.Create != status)
			{
				break;
			}
			
			mTotalSize = -1;
			try 
			{
				mTotalSize = getFileTotalSize();
			} 
			catch (IOException e) 
			{
				Logging.e(TAG, String.format("getFileTotalSize Error!e.getMessage:%s", e.getMessage()));
				
				reason = NetworkStatusReason.ConnectionError;
				break;
			}
			
			if (-1 == mTotalSize) 
			{
				reason = NetworkStatusReason.ConnectionError;
				break;
			}
			
			if(3 == mTotalSize){
				reason = NetworkStatusReason.AccountException;
				break;
			}
			
			mIsSupportRange = isSupportRange();
			reason = preparaFile(mTotalSize);
		}
		while(false);
		
		
		if (NetworkStatus.Create != status || NetworkStatusReason.OK != reason)
		{
			this.onProcess(getProcess(), NetworkStatus.Fail, reason);
			return false;
		}
		

		mHttpParameters = new BasicHttpParams();
		HttpConnectionParams.setConnectionTimeout(mHttpParameters, TIME_OUT);
		HttpConnectionParams.setSoTimeout(mHttpParameters, TIME_OUT);
		this.onProcess(getProcess(), NetworkStatus.Init, NetworkStatusReason.OK);
		return true;
	}
	
	private NetworkStatusReason download()
	{
		NetworkStatusReason reason = NetworkStatusReason.OK;
		
		byte[] buf = new byte[READ_BUF_SIZE];
		
		if (mIsSupportRange)
		{
			while (!mNeedCancel &&
					0 != mTotalSize - mCurrentSize)
			{
				reason = getFile(buf, mCurrentSize, mCurrentSize + READ_BLOCK_SIZE > mTotalSize ? mTotalSize - mCurrentSize : READ_BLOCK_SIZE);
				if (NetworkStatusReason.OK != reason) break;
			}
		}
		else
		{
			reason = getFile(buf, 0, mTotalSize);
		}
		
		return reason;
	}
	
	@Override
	public void start() 
	{
		new Thread(new Runnable() 
		{
			@Override
			public void run() 
			{
				if (NetworkStatus.Init != mNetworkInfo.getStatus()) 
				{
					if (!init()) return;
				}

				DownloadTask.this.onProcess(getProcess(), NetworkStatus.Process, NetworkStatusReason.OK);

				mProcessTimer = new Timer();
				mTimerTask = new ProcessTimerTask();
				mProcessTimer.scheduleAtFixedRate(mTimerTask, PROCESS_TIME_DELAY, PROCESS_TIME_DELAY);
				
				NetworkStatus status = NetworkStatus.Success;
				NetworkStatusReason reason = NetworkStatusReason.OK;

				reason = download();
				if (NetworkStatusReason.OK != reason) status = NetworkStatus.Fail;
				
				if (mNeedCancel)//用户取消
				{
					status = NetworkStatus.Cancel;
					reason = NetworkStatusReason.OK;
				}
				
				try 
				{
					if (null != mFile) mFile.close();
				} 
				catch (IOException e) 
				{
					Logging.e(TAG, String.format("close file Error!e.getMessage:%s", e.getMessage()));
					
					status = NetworkStatus.Fail;
					reason = NetworkStatusReason.IOError;
				}
				
				finish(status, reason);
			}
		}).start();
	}

	@Override
	public void finish(NetworkStatus status, NetworkStatusReason reason) 
	{
		mTimerTask.cancel();
		mProcessTimer.cancel();
		
		mNetworkInfo.setResult(mPath);
		this.onProcess(getProcess(), status, reason);
	}
	
	private class ProcessTimerTask extends TimerTask
	{
		public void run() 
		{
			DownloadTask.this.onProcess(getProcess(), NetworkStatus.NotChange, NetworkStatusReason.NotChange);
		}
		
	}
}
